using System.Collections.Generic;

namespace Gma.QrCodeNet.Encoding.DataEncodation;

public sealed class ECISet
{
	/// <summary>
	/// ISO/IEC 18004:2006 Chapter 6.4.2 Mode indicator = 0111 Page 23
	/// </summary>
	private const int ECIMode = 7;

	private const int ECIIndicatorNumBits = 4;

	private Dictionary<string, int> _nameToValue;
	private Dictionary<int, string> _valueToName;

	/// <summary>
	/// Initialize ECI Set.
	/// </summary>
	/// <param name="option">AppendOption is enum under ECISet
	/// Use NameToValue during Encode. ValueToName during Decode</param>
	internal ECISet(AppendOption option)
	{
		Initialize(option);
	}

	public enum AppendOption
	{
		NameToValue,
		ValueToName,
		Both
	}

	/// <summary>
	/// Length indicator for number of ECI codewords
	/// </summary>
	/// <remarks>ISO/IEC 18004:2006 Chapter 6.4.2 Page 24.
	/// 1 codeword length = 0. Any additional codeword add 1 to front. Eg: 3 = 110</remarks>
	/// <description>Bits required for each one is:
	/// one = 1, two = 2, three = 3</description>
	private enum ECICodewordsLength
	{
		One = 0,
		Two = 2,
		Three = 6
	}

	/// <remarks>ISO/IEC 18004:2006E ECI Designator Page 24</remarks>
	/// <param name="eCIValue">Range: 0 ~ 999999</param>
	/// <returns>Number of Codewords(Byte) for ECI Assignment Value</returns>
	private static int NumOfCodewords(int eCIValue)
	{
		if (eCIValue is >= 0 and <= 127)
		{
			return 1;
		}
		else if (eCIValue is > 127 and <= 16383)
		{
			return 2;
		}
		else if (eCIValue is > 16383 and <= 999999)
		{
			return 3;
		}
		else
		{
			throw new ArgumentOutOfRangeException($"{nameof(eCIValue)} should be in range: 0 to 999999.");
		}
	}

	/// <remarks>ISO/IEC 18004:2006E ECI Designator Page 24</remarks>
	/// <param name="eCIValue">Range: 0 ~ 999999</param>
	/// <returns>Number of bits for ECI Assignment Value</returns>
	private static int NumOfAssignmentBits(int eCIValue) => NumOfCodewords(eCIValue) * 8;

	private void AppendECI(string name, int value, AppendOption option)
	{
		switch (option)
		{
			case AppendOption.NameToValue:
				_nameToValue.Add(name, value);
				break;

			case AppendOption.ValueToName:
				_valueToName.Add(value, name);
				break;

			case AppendOption.Both:
				_nameToValue.Add(name, value);
				_valueToName.Add(value, name);
				break;

			default:
				throw new InvalidOperationException($"There is no such {nameof(AppendOption)}.");
		}
	}

	private void Initialize(AppendOption option)
	{
		switch (option)
		{
			case AppendOption.NameToValue:
				_nameToValue = new Dictionary<string, int>();
				break;

			case AppendOption.ValueToName:
				_valueToName = new Dictionary<int, string>();
				break;

			case AppendOption.Both:
				_nameToValue = new Dictionary<string, int>();
				_valueToName = new Dictionary<int, string>();
				break;

			default:
				throw new InvalidOperationException($"There is no such {nameof(AppendOption)}.");
		}

		// ECI table. Source 01 URL: http://strokescribe.com/en/ECI.html
		// ECI table. Source 02 URL: http://lab.must.or.kr/Extended-Channel-Interpretations-ECI-Encoding.ashx
		// ToDo. Fill up remaining missing table.
		AppendECI("iso-8859-1", 1, option);
		AppendECI("IBM437", 2, option);

		// AppendECI("iso-8859-1", 3, option);	//ECI value 1 is default encoding.
		AppendECI("iso-8859-2", 4, option);
		AppendECI("iso-8859-3", 5, option);
		AppendECI("iso-8859-4", 6, option);
		AppendECI("iso-8859-5", 7, option);
		AppendECI("iso-8859-6", 8, option);
		AppendECI("iso-8859-7", 9, option);
		AppendECI("iso-8859-8", 10, option);
		AppendECI("iso-8859-9", 11, option);
		AppendECI("windows-874", 13, option);
		AppendECI("iso-8859-13", 15, option);
		AppendECI("iso-8859-15", 17, option);
		AppendECI("shift_jis", 20, option);
		AppendECI("utf-8", 26, option);
	}

	/// <remarks>ISO/IEC 18004:2006E ECI Designator Page 24</remarks>
	/// <param name="eCIValue">Range: 0 ~ 999999</param>
	/// <returns>Number of bits for ECI Header</returns>
	internal static int NumOfECIHeaderBits(int eCIValue) => NumOfAssignmentBits(eCIValue) + 4;

	internal int GetECIValueByName(string encodingName)
	{
		if (_nameToValue is null)
		{
			Initialize(AppendOption.NameToValue);
		}

		if (_nameToValue!.TryGetValue(encodingName, out int eCIValue))
		{
			return eCIValue;
		}
		else
		{
			throw new ArgumentOutOfRangeException($"ECI does not contain encoding: {encodingName}.");
		}
	}

	internal string GetECINameByValue(int eCIValue)
	{
		if (_valueToName is null)
		{
			Initialize(AppendOption.ValueToName);
		}

		if (_valueToName!.TryGetValue(eCIValue, out var eCIName))
		{
			return eCIName;
		}
		else
		{
			throw new ArgumentOutOfRangeException($"ECI does not contain value: {eCIValue}.");
		}
	}

	/// <returns>ECI table in Dictionary collection</returns>
	public Dictionary<string, int> GetECITable()
	{
		if (_nameToValue is null)
		{
			Initialize(AppendOption.NameToValue);
		}

		return _nameToValue;
	}

	public bool ContainsECIName(string encodingName)
	{
		if (_nameToValue is null)
		{
			Initialize(AppendOption.NameToValue);
		}

		return _nameToValue!.ContainsKey(encodingName);
	}

	public bool ContainsECIValue(int eciValue)
	{
		if (_valueToName is null)
		{
			Initialize(AppendOption.ValueToName);
		}

		return _valueToName!.ContainsKey(eciValue);
	}

	/// <remarks>ISO/IEC 18004:2006 Chapter 6.4.2 Page 24.</remarks>
	internal BitList GetECIHeader(string encodingName)
	{
		int eciValue = GetECIValueByName(encodingName);

		BitList dataBits = new()
		{
			{ ECIMode, ECIIndicatorNumBits }
		};

		int eciAssignmentByte = NumOfCodewords(eciValue);

		// Number of bits = Num codewords indicator + codeword value = Number of codewords * 8
		// Chapter 6.4.2.1 ECI Designator ISOIEC 18004:2006 Page 24
		int eciAssignmentBits;
		switch (eciAssignmentByte)
		{
			case 1:
				// Indicator = 0. Page 24. Chapter 6.4.2.1
				dataBits.Add((int)ECICodewordsLength.One, 1);
				eciAssignmentBits = (eciAssignmentByte * 8) - 1;
				break;

			case 2:
				// Indicator = 10. Page 24. Chapter 6.4.2.1
				dataBits.Add((int)ECICodewordsLength.Two, 2);
				eciAssignmentBits = (eciAssignmentByte * 8) - 2;
				break;

			case 3:
				// Indicator = 110. Page 24. Chapter 6.4.2.1
				dataBits.Add((int)ECICodewordsLength.Three, 3);
				eciAssignmentBits = (eciAssignmentByte * 8) - 3;
				break;

			default:
				throw new InvalidOperationException("Assignment Codewords should be either 1, 2 or 3.");
		}

		dataBits.Add(eciValue, eciAssignmentBits);

		return dataBits;
	}
}
